//
//  SiXCTextView.m
//  scheme-indent-xcplugin
//
//  Created by Ende on 10/12/29.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "SiXCTextView.h"
#import <objc/runtime.h>
#import "SiXCSourceModelItem.h"
#import "SiXCTextStorage.h"
#import "SiTools.h"

static NSInteger tabWidthAt(NSInteger num, NSInteger width) {
	if (width == 0) return 0;
	return width - (num % width);
	//	NSInteger mod = num % width;
	//	if (mod == 0) return width;
	//	return width - mod;
}

//	string に対し、tab幅を考慮して、その width を計算する
//	全角に対してはもちろんズレるが、気にする奴はいまい
NSInteger calcStringWidth(NSString *string, NSInteger tabWidth) {
	CFStringRef str = (CFStringRef)string;
	CFStringInlineBuffer buf;
	const CFIndex len = CFStringGetLength(str);
	CFStringInitInlineBuffer(str, &buf, CFRangeMake(0, len));
	
	NSInteger temp = 0;
	for (int i = 0; i < len; i++) {
		UniChar ch = CFStringGetCharacterFromInlineBuffer(&buf, i);
		
		if (ch == 0x09) temp += tabWidthAt(temp, tabWidth);
		else if (CFStringIsSurrogateLowCharacter(ch)) continue;	//	念のため。
		else if (ch == '\n' && len-1 == i) continue;	//	don't count last '\n' (new line)
		else temp++;
		//printf("%d : %d : %04X\n", i, temp, ch);
	}
	NSLog(@"calcStringLength(%@, %lld) = %lld", string, tabWidth, temp);
	return temp;
}

static NSArray *__langArray1 = nil;
static NSArray *__langArray2 = nil;


@implementation XCTextView (SiPlugin)

+ (void)load {
	[super load];
	
	if(!__langArray1) __langArray1 =
		[[NSArray alloc] initWithObjects: @"my.lang.scheme", @"lang.scheme.expression", @"lang.scheme.def-expr", @"lang.scheme.def-macro", nil];
	//	
	if(!__langArray2) __langArray2 =
		[[NSArray alloc] initWithObjects: @"cond", @"and", @"or", @"not", @"if", @"unless", @"when", nil];
}

- (id)_textStorage {

	id obj = [self textStorage];
	return obj;
}

- (void)_indent:(NSUInteger)indent {
	//	NSUndoManager をハックして得られた挙動を再現。
	//	動作の意味は不明だが、このオマジナイで Undo 時の挙動が改善される。
	
	XCFoldingTextStorage *textStorage = [self textStorage];
	NSUndoManager *undoManager = [self undoManager];
	NSArray *selectedRanges = [self selectedRanges];
	if ([selectedRanges count] < 1) return;
	
	const NSUInteger selectedLocation = [[selectedRanges objectAtIndex: 0] rangeValue].location-1;
	
	XCUndoMultiEdits *mulEdits1 = [[XCUndoMultiEdits alloc] initWithAffectedRange:NSMakeRange(NSIntegerMax, 0)
																	layoutManager:nil
																	  undoManager:undoManager
																 replacementRange:NSMakeRange(selectedLocation-1, 0)];
	[undoManager registerUndoWithTarget:textStorage
							   selector:@selector(_undoRedoTextOperation:)
								 object:mulEdits1];
	//	箇所で実際のインデントが実行されている。
	[textStorage indentLine: [self _currentLineNumber]-1
						 to: indent
				undoManager: undoManager];
	/*[textStorage indentLine: [self _currentLineNumber]-1
		  onlyIfMovingRight: YES
				undoManager: undoManager];*/
	XCUndoMultiEdits *mulEdits2 = [[XCUndoMultiEdits alloc] initWithAffectedRange:NSMakeRange(selectedLocation, 0)
																	layoutManager:nil
																	  undoManager:undoManager
																 replacementRange:NSMakeRange(NSIntegerMax, 0)];
	[undoManager registerUndoWithTarget:textStorage
							   selector:@selector(_undoRedoTextOperation:)
								 object:mulEdits2];
	
}

- (void)_myIndentation {
	
	XCFoldingTextStorage *textStorage = [self textStorage];
	XCTextStorage *realStorage = [textStorage realStorage];
	//XCSourceModel *sourceModel = [realStorage sourceModel];
	
	NSArray *selectedRanges = [self selectedRanges];
	if ([selectedRanges count] < 1) return;
	const NSUInteger selectedLocation = [[selectedRanges objectAtIndex: 0] rangeValue].location-1;	//	return して発生した '\n' 部を除くための -1
		//	ここで .location-1 しないと、item や itemBeforeItem の計算に予期しない結果が現れた
	const NSUInteger selectedLine = [self _currentLineNumber]-1;	//	_currentLineNumber は Top行を1として返すため、-1 している
	//	selectedLine が 1以上でない場合は想像できないが、念のため、以下には (0 < selectedLine) チェックをしている箇所がある。
	
	NSUInteger resultIndent = 0;
	NSLog(@"%llu", selectedLocation);
	
	//	カーソルを含む位置の ModelItem
	XCSourceModelItem *item = [realStorage sourceModelItemAtCharacterIndex: selectedLocation];
	NSLog(@"item: %@", [item simpleDescription]);
	
	if ([__langArray1 containsString: [XCLanguageSpecification _identifierForUniqueId: [item langId]]]) {
		
		//	item の子要素で、カーソルの直前にある ModelItem
		NSUInteger itemBeforeItemIndex;
		XCSourceModelItem *itemBeforeItem = [item childBeforeLocation: selectedLocation index: &itemBeforeItemIndex];
		NSLog(@"itemBeforeItem: [%llu] %@", itemBeforeItemIndex, [itemBeforeItem simpleDescription]);
		
		if (itemBeforeItem) {
			//	itemBeforeItem の開始行
			NSUInteger itemBeforeItemLine = [textStorage lineRangeForCharacterRange: [itemBeforeItem range]].location;
			NSLog(@"itemBeforeItemLine: %llu", itemBeforeItemLine);
			NSUInteger itemBeforeItemLineLocation = [textStorage characterRangeForLineRange: NSMakeRange(itemBeforeItemLine, 1)].location;
			NSLog(@"itemBeforeItemLineLocation: %llu", itemBeforeItemLineLocation);
			NSUInteger topItemOfTheLineIndex;
			//	topItemOfTheLine := 前行において最も前にある、item の子要素
			XCSourceModelItem *topItemOfTheLine = [item childAfterLocation: itemBeforeItemLineLocation index: &topItemOfTheLineIndex];
			NSLog(@"topItemOfTheLine: [%llu] %@", topItemOfTheLineIndex, [topItemOfTheLine simpleDescription]);
			NSUInteger locationOfTheTopItem = [topItemOfTheLine range].location;
			NSLog(@"locationOfTheTopItem: %llu", locationOfTheTopItem);
			
			NSString *nameOfTheTopItem = [realStorage substringOfItem: topItemOfTheLine];
			NSLog(@"topItemOfTheLine: %@", nameOfTheTopItem);
			
			NSInteger indent = 0;
			{
				NSString *str = [[realStorage string] substringWithRange: NSMakeRange(itemBeforeItemLineLocation, locationOfTheTopItem-itemBeforeItemLineLocation)];
				//	indent := 要素 itemBeforeItem のインデント幅
				indent = calcStringWidth(str, [realStorage getTabWidth]);
			}
			//NSInteger indentOfTheTopItem = [sourceModel indentForItem: topItemOfTheLine];
			//NSLog(@"indentOfTheTopItem: %lld", indentOfTheTopItem);
			
			if ([item isEqualToIdentifier: @"my.lang.scheme"]) {
				//	item がトップレベルであるとき、前の要素のある行のインデント幅 indent に揃える
				NSLog(@"** 1 ITEM is my.lang.scheme");
				resultIndent = indent;
			} else if ([realStorage isItemInLetVarInit: item]) {
				//	item が let 文の変数設定部S式であり、また前要素 itemBeforeItem が存在するとき、itemBeforeItem のインデント幅に揃える
				NSLog(@"** 2");
				resultIndent = indent;
			} else {
				//	topItemOfTheLine が item の firstChild である場合
				if (topItemOfTheLineIndex == 0) {
					//	topItemOfTheLine が cond/and/if 等、引数を並列したい要素である場合
					if ([__langArray2 containsString: nameOfTheTopItem]) {
						/*XCSourceModelItem *nextItem = [topItemOfTheLine nextItem];
						if (nextItem) {
							
						}*/
						//	topItemOfTheLine の後ろに +1 してインデント
						NSLog(@"** 3");
						resultIndent = indent + [topItemOfTheLine range].length + 1;
						NSLog(@"resultIndent = %lld + %lld + 2 = %lld", indent, [topItemOfTheLine range].length, resultIndent);
					} else {
						NSLog(@"** 4");
						resultIndent = indent + 1;
					}
				} else {
					NSLog(@"** 5");
					resultIndent = indent;
				}
				
			}
			
			if ([item isEqualToIdentifier: @"lang.scheme.expression"]) {
				
			}
			
			
		} else {
			if ([realStorage isItemInLetVarInit: item]) {
				if (0 < selectedLine) {
					//	item が let 文の変数設定部S式であり、また前要素 itemBeforeItem が存在しないとき、前行の末尾に揃える。
					NSRange range = [realStorage characterRangeForLineRange: NSMakeRange(selectedLine-1, 1)];
					NSString *lastLineStr = [[realStorage string] substringWithRange: range];
					const NSInteger width = calcStringWidth(lastLineStr, [realStorage getTabWidth]);
					NSLog(@"** 6");
					resultIndent = width;
					NSLog(@"isItemInLetVarInit");
					NSLog(@"- range: %@ lastLineStr: %@ width: %lld", NSStringFromRange(range), lastLineStr, width);
				}
			} else {
				NSLog(@"** 7");
				//	default動作: 前行のインデント幅に揃える。or 0
				resultIndent = (0 < selectedLine) ? [textStorage leadingWhitespacePositionsForLine: selectedLine-1] : 0;
			}
		}
		NSLog(@"** resultIndent: %llu", resultIndent);
		[self _indent: resultIndent];
	}
	
	
}

- (void)__indentSelectionIfIndentable:(id)arg1 {
	
	@try {
		XCFoldingTextStorage *textStorage = [self textStorage];
		XCTextStorage *realStorage = [textStorage realStorage];
		
		if ([[realStorage language] isEqualToString: @"my.lang.scheme"]) {
			[self _myIndentation];
			return;
		}
	}
	@catch (NSException * e) {
		NSLog(@"@catch (NSException: %@)", e);
	}
	@finally {
		[self __indentSelectionIfIndentable:arg1];
	}
}

@end
